﻿// <summary>
//   A cached service for use with BDC model services.
// </summary>

namespace ATT.SharePoint.Patterns.BCS
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    /// <summary>
    /// Provides a caching mechanism for BCS external connectors.
    /// </summary>
    /// <typeparam name="T">Type of BDC entity to store in this cache.</typeparam>
    /// <typeparam name="I">Type of the identifier for the BDC entity.</typeparam>
    public class CachedConnectorService<T, I> where T : BDCEntity<I>
    {
        #region Private members

        private List<T> cache;
        private CachedConnectorParameters<T, I> parameters;

        private static object lockObject = new object();

        #endregion

        #region Constructor

        public CachedConnectorService(CachedConnectorParameters<T, I> parameters)
        {
            this.parameters = parameters;
        }

        #endregion

        #region Public methods

        /// <summary>
        /// Reads individual entity from the cache.
        /// </summary>
        /// <param name="identifier">Identifier of the entity to read.</param>
        /// <returns>BDC entity.</returns>
        public T ReadItem(I identifier)
        {
            this.LogToOperations(
                "Reading BDC Item: " + typeof(T).ToString() + ", identifier="
                + identifier);

            T entity = default(T);

            try
            {
                if (cache == null)
                {
                    this.LogToOperations(
                        typeof(T).ToString() + " cache is null, reloading cache from database.");
                    this.ReadList();
                }

                entity = this.GetFromCache(identifier);

                if (entity == null)
                {
                    this.LogToOperations(
                        "Identifier not found in local cache, getting from database.");
                    entity = this.GetFromDatabase(identifier, parameters.DatabaseCall);
                }
            }
            catch (Exception)
            {
                this.LogToOperations(
                    "Exception occured reading BDC Item: " + typeof(T).ToString() + ", Identifier=" + identifier);
            }

            return entity;
        }

        /// <summary>
        /// Reads list of entities into the cache.
        /// </summary>
        /// <returns>Collection of entities.</returns>
        public IEnumerable<T> ReadList()
        {
            if (cache == null)
            {
                lock (lockObject)
                {
                    try
                    {
                        if (cache == null)
                        {
                            this.LogToOperations(
                                "Getting list of " + typeof(T).ToString());

                            List<T> cacheTemp = new List<T>();
                            this.parameters.PopulateCache.Invoke(cacheTemp);

                            this.LogToOperations(
                                "Loaded " + cacheTemp.Count.ToString() + typeof(T).ToString());
                            cache = cacheTemp;
                        }
                    }
                    catch (Exception)
                    {
                        this.LogToOperations(
                                "Exception occured getting list of " + typeof(T).ToString());
                    }
                }
            }

            return cache.ToArray();
        }

        #endregion

        #region Private static methods

        /// <summary>
        /// Gets entity from the cache
        /// </summary>
        /// <param name="identifier">Identifier of entity to return.</param>
        /// <returns>Entity instance.</returns>
        private T GetFromCache(I identifier)
        {
            lock (lockObject)
            {
                return cache.Where
                    (
                        a => a.Identifier.Equals(identifier)
                    )
                    .FirstOrDefault();
            }
        }

        /// <summary>
        /// Gets entity from the database using the specified delegate.
        /// </summary>
        /// <param name="identifier">Identifier of entity to return.</param>
        /// <param name="databaseCall">Delegate that does the work of 
        /// retrieving entity from database</param>
        /// <returns>Entity instance.</returns>
        private T GetFromDatabase(I identifier, Func<I, T> databaseCall)
        {
            return databaseCall.Invoke(identifier);
        }

        private void LogToOperations(string message)
        {
            if (this.parameters.Logger != null)
            {
                this.parameters.Logger.LogToOperations(message);
            }
        }

        #endregion
    }
}
